package com.gitee.qdbp.jdbc.upgrade;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.gitee.qdbp.able.debug.ConsoleDebugger;
import com.gitee.qdbp.able.debug.Debugger;
import com.gitee.qdbp.eachfile.process.BaseFileProcessor;
import com.gitee.qdbp.eachfile.runner.FileEachRunner;
import com.gitee.qdbp.jdbc.operator.DbBaseOperator;
import com.gitee.qdbp.jdbc.plugins.DbPluginContainer;
import com.gitee.qdbp.tools.files.FileTools;
import com.gitee.qdbp.tools.files.PathTools;
import com.gitee.qdbp.tools.utils.StringTools;

/**
 * 版本升级工具类
 *
 * @author zhaohuihua
 * @version 20210620
 */
public class UpgradeProcessor extends BaseFileProcessor {

    public static void process(String rootFolder) {
        Debugger debugger = new ConsoleDebugger();
        UpgradeProcessor proccessor = new UpgradeProcessor();
        new FileEachRunner(rootFolder, proccessor, debugger).start();
    }

    protected UpgradeProcessor() {
        super(true, false);
        this.setExcludeFolders(".svn", "target", ".settings");
        this.setIncludeFileTypes("java");
    }

    @Override
    protected void doFileProcess(String root, File file) {
        String path = PathTools.relativize(root, file.getAbsolutePath());
        String content;
        try {
            content = FileTools.readTextContent(file);
        } catch (IOException e) {
            this.debug("[ERROR]Failed to read text content, {} {}", path, e.getMessage());
            return;
        }
        if (isNeedUpgrade(content)) {
            String newContent = replace(content);
            if (newContent != null) {
                try {
                    FileTools.saveFile(newContent, file);
                    this.debug(path);
                } catch (IOException e) {
                    this.debug("[ERROR]Failed to write text content, {} {}", path, e.getMessage());
                }
            }
        }
    }

    private String replace(String string) {
        String next = string;
        String result = null;
        String temp;

        temp = replaceSubstring(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceBootMethod(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceWhereOn(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceWhereLogic(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceWhereBizCondition(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceNewWhereInline(next);
        if (temp != null) {
            next = temp;
            result = temp;
        }

        temp = replaceUpdateSet(next);
        if (temp != null) {
            result = temp;
        }

        return result;
    }

    /** 替换固定短语 **/
    private static String replaceSubstring(String string) {
        string = StringTools.replace(string, "new EmptiableWhere()", "DbWhere.newEmptiableWhere()");
        string = StringTools.replace(string, "import com.gitee.qdbp.able.jdbc.condition.DbWhere.EmptiableWhere;", "");
        return string;
    }

    private static final Pattern BOOT_METHOD = Pattern.compile(
        "(buildCrudDao|buildJoinQuery|buildSqlBuilder|getSqlDao|getSqlDialect|getSqlBufferJdbcOperations)");

    private static final Map<String, String> BOOT_METHODS = new HashMap<>();
    static {
        BOOT_METHODS.put("buildCrudDao", "crudDao");
        BOOT_METHODS.put("buildJoinQuery", "joinQueryer");
        BOOT_METHODS.put("buildSqlBuilder", "sqlBuilder");
        BOOT_METHODS.put("getSqlDao", "sqlDao");
        BOOT_METHODS.put("getSqlDialect", "sqlDialect");
        BOOT_METHODS.put("getSqlBufferJdbcOperations", "sqlJdbc");
    }

    private String replaceBootMethod(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = BOOT_METHOD.matcher(string);
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }

            String oldName = matcher.group();
            String newName = BOOT_METHODS.get(oldName);
            if (newName == null) {
                buffer.append(oldName);
            } else {
                buffer.append(newName);
            }
            index = matcher.end();
        }
        if (index == 0) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private static final Pattern WHERE_ON =
            Pattern.compile("(\\w+)?\\.on\\((\"?[^\", \t]+\"?)\\s*,\\s*\"?([^\",\t]+)\"?\\s*(,\\s*|\\))");

    private String replaceWhereOn(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = WHERE_ON.matcher(string);
        boolean changed = false;
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }
            if (replaceWhereOn(buffer, matcher)) {
                changed = true;
            }
            index = matcher.end();
        }
        if (!changed) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private boolean replaceWhereOn(StringBuilder buffer, Matcher matcher) {
        int group = 1;
        String varName = matcher.group(group++);
        String fieldName = matcher.group(group++);
        String operatorType = matcher.group(group++);
        String suffix = matcher.group(group);
        DbBaseOperator operator = DbPluginContainer.defaults().helper().getWhereOperator(operatorType);
        if (operator == null) {
            buffer.append(matcher.group());
            return false;
        }
        if (varName == null) {
            // .on(fieldName, operatorType,
            String logicType = lastIsBrackets(buffer) ? "on" : "and";
            buffer.append('.').append(logicType);
        } else {
            // where.on(fieldName, operatorType,
            buffer.append(varName).append(".on");
        }
        buffer.append('(').append(fieldName).append(')');
        String operatorName = toLeadingLowerCase(operator.getName());
        buffer.append('.').append(operatorName).append('(');
        if (")".equals(suffix)) {
            buffer.append(')');
        }
        return true;
    }

    // .andEquals("userType", UserType.USER)
    // 替换成
    // .and("userType").equals(UserType.USER)
    private static final Pattern WHERE_LOGIC =
            Pattern.compile("(\\w+)?\\.(where|on|and|or)([A-Z]\\w+)\\((\"?[^\", \t]+\"?)\\s*(,\\s*|\\))");

    private String replaceWhereLogic(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = WHERE_LOGIC.matcher(string);
        boolean changed = false;
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }
            if (replaceWhereLogic(buffer, matcher)) {
                changed = true;
            }
            index = matcher.end();
        }
        if (!changed) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private boolean replaceWhereLogic(StringBuilder buffer, Matcher matcher) {
        int group = 1;
        String varName = matcher.group(group++);
        String logicType = matcher.group(group++);
        String operatorType = matcher.group(group++);
        String fieldName = matcher.group(group++);
        String suffix = matcher.group(group);
        if ("where".equals(logicType) && "By".equals(operatorType)) {
            buffer.append(matcher.group());
            return false;
        }
        DbBaseOperator operator = DbPluginContainer.defaults().helper().getWhereOperator(operatorType);
        if (operator == null) {
            buffer.append(matcher.group());
            return false;
        }
        if (varName == null) {
            // .andEquals(fieldName,
            if ("and".equals(logicType) && lastIsBrackets(buffer)) {
                logicType = "on";
            }
            buffer.append('.').append(logicType);
        } else {
            // where.andEquals(fieldName,
            buffer.append(varName).append(".on");
        }
        if ("SubCondition".equals(operatorType) || "NotSubCondition".equals(operatorType)) {
            // .andSubCondition(
            buffer.append("()");
            buffer.append('.').append(toLeadingLowerCase(operatorType));
            buffer.append("(").append(fieldName);
            if (")".equals(suffix)) {
                buffer.append(')');
            }
        } else {
            buffer.append('(').append(fieldName).append(')');
            String operatorName = toLeadingLowerCase(operator.getName());
            buffer.append('.').append(operatorName).append('(');
            if (")".equals(suffix)) {
                buffer.append(')');
            }
        }
        return true;
    }

    // .andBizCondition
    // 替换成
    // .and().bizCondition
    private static final Pattern WHERE_BIZ_CONDITION =
            Pattern.compile("(\\w+)?\\.(on|and|or)(BizCondition|NotBizCondition|SubCondition|NotSubCondition)");

    private String replaceWhereBizCondition(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = WHERE_BIZ_CONDITION.matcher(string);
        boolean changed = false;
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }
            if (replaceWhereBizCondition(buffer, matcher)) {
                changed = true;
            }
            index = matcher.end();
        }
        if (!changed) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private boolean replaceWhereBizCondition(StringBuilder buffer, Matcher matcher) {
        int group = 1;
        String varName = matcher.group(group++);
        String logicType = matcher.group(group++);
        String operatorType = matcher.group(group);
        if (varName == null) {
            // .andBizCondition
            if ("and".equals(logicType) && lastIsBrackets(buffer)) {
                logicType = "on";
            }
            buffer.append('.').append(logicType);
        } else {
            // where.andBizCondition
            buffer.append(varName).append(".on");
        }
        buffer.append("()");
        buffer.append('.').append(toLeadingLowerCase(operatorType));
        return true;
    }

    // DbWhere where = new DbWhere().on("s.projectCode").equals("P0000001");
    // 增加.end()
    // DbWhere where = new DbWhere().on("s.projectCode").equals("P0000001").end();
    private static final Pattern WHERE_INLINE =
            Pattern.compile("DbWhere\\s+\\w+\\s*=\\s*new\\s+DbWhere\\(\\s*\\)\\s*\\.[^;{}\\r\\n]+");

    private String replaceNewWhereInline(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = WHERE_INLINE.matcher(string);
        boolean changed = false;
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }
            buffer.append(matcher.group());
            if (string.charAt(matcher.end()) == ';' && !lastIsBrackets(buffer)) {
                changed = true;
                buffer.append(".end()");
            }
            index = matcher.end();
        }
        if (!changed) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private static final Pattern UPDATE_SET = Pattern.compile("(ud|update|Update)\\.(set|add)\\((\"?[^\", \t]+\"?)\\s*,\\s*");

    private String replaceUpdateSet(String string) {
        StringBuilder buffer = new StringBuilder();
        Matcher matcher = UPDATE_SET.matcher(string);
        boolean changed = false;
        int index = 0;
        while (matcher.find()) {
            if (index < matcher.start()) {
                buffer.append(string, index, matcher.start());
            }
            if (replaceUpdateSet(buffer, matcher)) {
                changed = true;
            }
            index = matcher.end();
        }
        if (!changed) {
            return null;
        }
        if (index < string.length()) {
            buffer.append(string.substring(index));
        }
        return buffer.toString();
    }

    private boolean replaceUpdateSet(StringBuilder buffer, Matcher matcher) {
        int group = 1;
        String varName = matcher.group(group++);
        String operatorType = matcher.group(group++);
        String fieldName = matcher.group(group);

        if ("set".equals(operatorType)) {
            buffer.append(varName).append(".set(").append(fieldName).append(").to(");
            return true;
        } else if ("add".equals(operatorType)) {
            buffer.append(varName).append(".set(").append(fieldName).append(").add(");
            return true;
        } else {
            buffer.append(matcher.group());
            return false;
        }
    }

    private static boolean lastIsBrackets(StringBuilder buffer) {
        int index = buffer.length() - 1;
        for (; index >= 0; index--) {
            char c = buffer.charAt(index);
            if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                continue;
            } else {
                break;
            }
        }
        if (index >= 0 && buffer.charAt(index--) != ')') {
            return false;
        }
        for (; index >= 0; index--) {
            char c = buffer.charAt(index);
            if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                continue;
            } else {
                break;
            }
        }
        return index < 0 || buffer.charAt(index) == '(';
    }

    private static String toLeadingLowerCase(final String string) {
        int len;
        if (string == null || (len = string.length()) == 0) {
            return string;
        }

        char firstChar = string.charAt(0);
        if (Character.isLowerCase(firstChar)) {
            return string;
        }

        return new StringBuilder(len).append(Character.toLowerCase(firstChar)).append(string.substring(1)).toString();
    }

    private boolean isNeedUpgrade(String string) {
        List<String> lines = StringTools.splits(string, false, '\n');
        for (String line : lines) {
            String trimed = line.trim();
            if (trimed.startsWith("//")) {
                continue;
            }
            if (trimed.contains("import")) {
                if (trimed.contains("DbWhere")) {
                    return true;
                }
                if (trimed.contains("EmptiableWhere")) {
                    return true;
                }
                if (trimed.contains("DbUpdate")) {
                    return true;
                }
                if (trimed.contains("QdbcBoot")) {
                    return true;
                }
            }
            if (trimed.startsWith("public") || trimed.startsWith("class") || trimed.startsWith("enum")
                    || trimed.startsWith("interface")) {
                return false;
            }
        }
        return false;
    }
}
